package tech.yixiyun.framework.kuafu.kits;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.databind.type.ArrayType;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.util.StdDateFormat;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Time;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

public class JSONKit {


    public static final ObjectMapper MAPPER = new ObjectMapper();

    static {
        MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        //反序列化时，未知属性不报异常
        MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        //允许注释
        MAPPER.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
        MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        SimpleModule module = new SimpleModule();
        module.addDeserializer(Date.class, new DateBasedDeserializer(null));
        module.addDeserializer(Time.class, new TimeDeserializer(null));
        //long类型转json时，转成字符串形式，否则会精度丢失
        ToStringSerializer stringSerializer = new ToStringSerializer();
        module.addSerializer(Long.class, stringSerializer);
        module.addSerializer(Long.TYPE, stringSerializer);
        MAPPER.registerModule(module);
    }



    /**
     * 获取JavaType工厂类，可以产出jackson类型转换用的JavaType
     *
     * @return
     */
    public static TypeFactory getTypeFactory() {
        return MAPPER.getTypeFactory();
    }




    /**
     * 将对象转成json格式字符串
     *
     * @param o
     * @return
     */
    public static String toJson(Object o) {
        if (o == null) {
            return null;
        }
        try {
            return MAPPER.writeValueAsString(o);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 将json字符串转成对象
     *
     * @param json
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T toObject(String json, Class<T> clazz) {
        if (StringKit.isBlank(json)) {
            return null;
        }
        try {
            return MAPPER.readValue(json, clazz);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * json字符串转任意对象
     *
     * @param json
     * @param type 这个需要调用getTypeFactory为想要转换的类型产出一个包装类，也就是JavaType，详情查看jackson使用文档
     * @return
     */
    public static Object toObject(String json, JavaType type) {
        try {
            return MAPPER.readValue(json, type);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * json字符串转对象数组
     *
     * @param json
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T[] toObjectArray(String json, Class<T> clazz) {
        ArrayType type = MAPPER.getTypeFactory().constructArrayType(clazz);
        return (T[]) toObject(json, type);
    }


    /**
     * json字符串转int类型数组
     *
     * @param json
     * @return
     */
    public static int[] toIntArray(String json) {
        ArrayType type = MAPPER.getTypeFactory().constructArrayType(int.class);
        return (int[]) toObject(json, type);
    }

    /**
     * json字符串转double类型数组
     *
     * @param json
     * @return
     */
    public static double[] toDoubleArray(String json) {
        ArrayType type = MAPPER.getTypeFactory().constructArrayType(double.class);
        return (double[]) toObject(json, type);
    }

    /**
     * json字符串转String类型数组
     *
     * @param json
     * @return
     */
    public static String[] toStringArray(String json) {
        ArrayType type = MAPPER.getTypeFactory().constructArrayType(String.class);
        return (String[]) toObject(json, type);
    }

    /**
     * json字符串转List结构
     *
     * @param json
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> List<T> toObjectList(String json, Class<T> clazz) {
        CollectionType collectionType = getTypeFactory().constructCollectionType(List.class, clazz);
        return (List<T>) toObject(json, collectionType);
    }

    /**
     * json字符串转整数List
     *
     * @param json
     * @return
     */
    public static List<Integer> toIntList(String json) {
        return toObjectList(json, Integer.class);
    }

    /**
     * json字符串转字符串List
     *
     * @param json
     * @return
     */
    public static List<String> toStringList(String json) {
        return toObjectList(json, String.class);
    }

    /**
     * json字符串转Double List
     *
     * @param json
     * @return
     */
    public static List<Double> toDoubleList(String json) {
        return toObjectList(json, Double.class);
    }

    /**
     * json字符串转Map结构
     *
     * @param json
     * @param keyClass
     * @param valueClass
     * @param <K>
     * @param <V>
     * @return
     */
    public static <K, V> Map<K, V> toObjectMap(String json, Class<K> keyClass, Class<V> valueClass) {
        MapType mapType = getTypeFactory().constructMapType(Map.class, keyClass, valueClass);
        return (Map<K, V>) toObject(json, mapType);
    }

    /**
     * json字符串转key是String类型的Map结构数据
     *
     * @param json
     * @param valueClass
     * @param <V>
     * @return
     */
    public static <V> Map<String, V> toStringKeyMap(String json, Class<V> valueClass) {
        return toObjectMap(json, String.class, valueClass);
    }

    /**
     * json字符串转 key和value都是String类型的Map结构数据
     *
     * @param json
     * @return
     */
    public static Map<String, String> toStringMap(String json) {
        return toObjectMap(json, String.class, String.class);
    }


    /**
     * 将json字符串转成树结构的json对象
     *
     * @param json
     * @return
     */
    public static JsonNode toJsonObject(String json) {
        try {
            return MAPPER.readTree(json);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 从json结构数据中，根据key读取数据并转成clazz类型。
     * key和js的写法一致，比如 a.b[1] ,通过.读取属性，通过[]读取数组元素
     *
     * @param json
     * @param key
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getValueAs(JsonNode json, String key, Class<T> clazz) {
        key = "/" + key.replace(".", "/").replace("[", "/").replace("]", "");
        json = json.at(key);
        if (json.isMissingNode()) {
            throw new RuntimeJsonMappingException(key + "对应的节点不存在，请检查拼写是否正确");
        }
        if (json.isObject() && clazz == String.class) {
            return (T) json.toString();
        }
        return toObject(json.toString(), clazz);
    }

    /**
     * 从json结构数据中，根据key读取数据并转成String类型。
     * key和js的写法一致，比如 a.b[1] ,通过.读取属性，通过[]读取数组元素
     *
     * @param json
     * @param key
     * @return
     */
    public static String getValueAsString(JsonNode json, String key) {
        return getValueAs(json, key, String.class);
    }

    /**
     * 根据key从json对象中取出数据，并自动封装成Integer类型
     *
     * @param json
     * @param key
     * @return
     * @throws Exception
     */
    public static Integer getValueAsInteger(JsonNode json, String key) throws Exception {
        return getValueAs(json, key, Integer.class);
    }

    /**
     * 根据key从json对象中取出数据，并自动封装成Double类型
     *
     * @param json
     * @param key
     * @return
     * @throws Exception
     */
    public static Double getValueAsDouble(JsonNode json, String key) throws Exception {
        return getValueAs(json, key, Double.class);
    }

    /**
     * 根据key从json对象中取出数据，并自动封装成Boolean类型
     *
     * @param json
     * @param key
     * @return
     * @throws Exception
     */
    public static Boolean getValueAsBoolean(JsonNode json, String key) throws Exception {
        return getValueAs(json, key, Boolean.class);
    }


    /**
     * 根据key 从 json对象中取出数据，并自动封装成 clazz类型的List
     *
     * @param json
     * @param key
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> List<T> getValueAsList(JsonNode json, String key, Class<T> clazz) {
        key = "/" + key.replace(".", "/").replace("[", "/").replace("]", "");
        json = json.at(key);
        if (json.isMissingNode()) {
            throw new RuntimeJsonMappingException(key + "对应的节点不存在，请检查拼写是否正确");
        }
        if (json.isArray() == false) {
            throw new RuntimeJsonMappingException(key + "对应的节点不是数组类型，无法转换");
        }

        return toObjectList(json.toString(), clazz);
    }


    /**
     * 根据key从json对象中取出数据，并自动封装成String类型List
     *
     * @param json
     * @param key
     * @return
     */
    public static List<String> getValueAsStringList(JsonNode json, String key) {
        return getValueAsList(json, key, String.class);
    }

    /**
     * 根据key从json对象中取出数据，并自动封装成Integer类型List
     *
     * @param json
     * @param key
     * @return
     */
    public static List<Integer> getValueAsIntegerList(JsonNode json, String key) {
        return getValueAsList(json, key, Integer.class);
    }

    /**
     * 根据key从json对象中取出数据，并自动封装成Boolean类型List
     *
     * @param json
     * @param key
     * @return
     */
    public static List<Boolean> getValueAsBooleanList(JsonNode json, String key) {
        return getValueAsList(json, key, Boolean.class);
    }

    /**
     * 根据key从json对象中取出数据，并自动封装成Double类型List
     *
     * @param json
     * @param key
     * @return
     */
    public static List<Double> getValueAsDoubleList(JsonNode json, String key) {
        return getValueAsList(json, key, Double.class);
    }

    /**
     * 将一个json对象和新的json字符串进行合并，原对象有的保留，没有的填进来。返回一个新的json对象。
     * 新的字符串从输入流中读取
     * @param original
     * @param newContent
     * @return
     */
    public static JsonNode merge(JsonNode original, InputStream newContent) {
        ObjectReader objectReader = MAPPER.readerForUpdating(original);
        try {
            return objectReader.readValue(newContent);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 将一个json对象和新的json字符串进行合并，原对象有的保留，没有的填进来。返回一个新的json对象。
     * @param original
     * @param newContent
     * @return
     */
    public static JsonNode merge(JsonNode original, String newContent) {
        ObjectReader objectReader = MAPPER.readerForUpdating(original);
        try {
            return objectReader.readValue(newContent);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 将一个json对象和新的json对象进行合并，原对象有的保留，没有的填进来。返回一个新的json对象。
     * @param original
     * @param newContent
     * @return
     */
    public static JsonNode merge(JsonNode original, JsonNode newContent) {
        ObjectReader objectReader = MAPPER.readerForUpdating(original);
        try {
            return objectReader.readValue(newContent);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 构造一个用于反序列化的List类型JavaType，
     * @param elementClass List中元素的类型
     * @return
     */
    public static JavaType createListType(Class elementClass) {
        return getTypeFactory().constructCollectionType(List.class, elementClass);
    }

    /**
     * 构造一个用于反序列化的Map类型JavaType
     * @param keyClass
     * @param valueClass
     * @return
     */
    public static JavaType createMapType(Class keyClass, Class valueClass) {
        return getTypeFactory().constructMapType(Map.class, keyClass, valueClass);
    }

    /**
     * 构造一个用于反序列化的数组类型JavaType，
     * @param elementClass 数组中元素的类型
     * @return
     */
    public static JavaType createArrayType(Class elementClass) {
        return getTypeFactory().constructArrayType(elementClass);
    }


    /**
     * 日期反序列化器，仿照DateSerializers.DateBasedDeserializer 实现的。
     * 它的逻辑就是，如果检测到有jsonformat注解，就用根据注解 实例化一个新的 反序列化器 来解析时间，否则就根据自定义的规则解析
     */
    protected static  class DateBasedDeserializer extends StdScalarDeserializer
            implements ContextualDeserializer {

        protected final DateFormat _customFormat;
        protected final String _formatString;
        protected DateBasedDeserializer(Class<?> clz) {
            super(clz);
            _customFormat = null;
            _formatString = null;
        }

        protected DateBasedDeserializer(DateBasedDeserializer base,
                                        DateFormat format, String formatStr) {
            super(base._valueClass);
            _customFormat = format;
            _formatString = formatStr;
        }

        protected  DateBasedDeserializer withDateFormat(DateFormat df, String formatStr) {
            return new DateBasedDeserializer(this, df, formatStr);
        }

        @Override
        public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            String date = p.readValueAs(String.class);
            if (_customFormat != null) {
                try {
                    return _customFormat.parse(date);
                } catch (ParseException e) {
                    throw new RuntimeException(e);
                }
            }
            return DateKit.parseDate(date);
        }

        @Override
        public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
            final JsonFormat.Value format = findFormatOverrides(ctxt, property,
                    handledType());

            if (format != null) {
                TimeZone tz = format.getTimeZone();
                final Boolean lenient = format.getLenient();

                //jackson源码 First: fully custom pattern?
                if (format.hasPattern()) {
                    final String pattern = format.getPattern();
                    final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
                    SimpleDateFormat df = new SimpleDateFormat(pattern, loc);
                    if (tz == null) {
                        tz = TimeZone.getTimeZone("GMT+8");
                    }
                    df.setTimeZone(tz);
                    if (lenient != null) {
                        df.setLenient(lenient);
                    }
                    return withDateFormat(df, pattern);
                }
                // But if not, can still override timezone
                if (tz != null) {
                    DateFormat df = ctxt.getConfig().getDateFormat();
                    // one shortcut: with our custom format, can simplify handling a bit
                    if (df.getClass() == StdDateFormat.class) {
                        final Locale loc = format.hasLocale() ? format.getLocale() : ctxt.getLocale();
                        StdDateFormat std = (StdDateFormat) df;
                        std = std.withTimeZone(tz);
                        std = std.withLocale(loc);
                        if (lenient != null) {
                            std = std.withLenient(lenient);
                        }
                        df = std;
                    } else {
                        // otherwise need to clone, re-set timezone:
                        df = (DateFormat) df.clone();
                        df.setTimeZone(tz);
                        if (lenient != null) {
                            df.setLenient(lenient);
                        }
                    }
                    return withDateFormat(df, _formatString);
                }
                // or maybe even just leniency?
                if (lenient != null) {
                    DateFormat df = ctxt.getConfig().getDateFormat();
                    String pattern = _formatString;
                    // one shortcut: with our custom format, can simplify handling a bit
                    if (df.getClass() == StdDateFormat.class) {
                        StdDateFormat std = (StdDateFormat) df;
                        std = std.withLenient(lenient);
                        df = std;
                        pattern = std.toPattern();
                    } else {
                        // otherwise need to clone,
                        df = (DateFormat) df.clone();
                        df.setLenient(lenient);
                        if (df instanceof SimpleDateFormat) {
                            ((SimpleDateFormat) df).toPattern();
                        }
                    }
                    if (pattern == null) {
                        pattern = "[unknown]";
                    }
                    return withDateFormat(df, pattern);
                }
            }
            return this;
        }
    }


    /**
     * Sql时间反序列化器
     */
    protected static class TimeDeserializer extends DateBasedDeserializer {
        protected TimeDeserializer(Class<?> clz) {
            super(clz);
        }

        protected TimeDeserializer(TimeDeserializer timeDeserializer, DateFormat df, String formatStr) {
            super(timeDeserializer, df, formatStr);
        }

        protected  TimeDeserializer withDateFormat(DateFormat df, String formatStr) {
            return new TimeDeserializer(this, df, formatStr);
        }

        @Override
        public Time deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            String date = p.readValueAs(String.class);
            if (_customFormat != null) {
                try {
                    return new Time(_customFormat.parse(date).getTime());
                } catch (ParseException e) {
                    throw new RuntimeException(e);
                }
            }

            return new Time(DateKit.parseDate(date).getTime());

        }
    }

}
